package ru.spbau.lifeincube.swing;

import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.embed.swing.JFXPanel;
import javafx.event.EventHandler;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import ru.spbau.lifeincube.gui3d.ArcBall;
import ru.spbau.lifeincube.gui3d.Cube;
import ru.spbau.lifeincube.logic.Cell;
import ru.spbau.lifeincube.math.Matrix3x3;
import ru.spbau.lifeincube.math.Matrix4x4;
import ru.spbau.lifeincube.math.Quaternion;

import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;


public class BaseFrame extends JFrame {

	private FrameActionListener mListener;
	private static final String FILE_SUFFIX_CONFIG = ".config";

	/**
	 *
	 */
	private static final long serialVersionUID = 1L;
    private final int mSize = 10;

    JFileChooser mFileChooser;
	SettingsDialog mParamsDialog;


	Icon mIconPrev = new ImageIcon("res/prev.png");
	Icon mIconNext = new ImageIcon("res/next.png");
	Icon mIconPlay = new ImageIcon("res/play.png");
	Icon mIconPause = new ImageIcon("res/pause.png");

	JButton mPrevBtn = new JButton(mIconPrev);
	JButton mNextBtn = new JButton(mIconNext);
	JButton mPlayBtn = new JButton(mIconPlay);
	JLabel mStepsCount = new JLabel("Steps count : ");


	JFXPanel panel;
	private Group sceneContent;
	private Group content;
	private final LifeAnimation lifeAnimation = new LifeAnimation();
	private final ArrayList<Timeline> animations = new ArrayList<>();
	private final boolean needAnimation = false;
	private final boolean allCube = false;


	private ArcBall arcBall;
	private Group root;
	private Cube[][][] field = null;
	private Matrix4x4 rotationMatrix = new Matrix4x4();

	private Controller controller;
	private double sceneScale = 1.0;

	ActionListener mOpenItemListener = new ActionListener() {

		@Override
		public void actionPerformed(ActionEvent e) {
			int retval = mFileChooser.showOpenDialog(BaseFrame.this);
			if (retval == JFileChooser.APPROVE_OPTION) {
				mListener.openFile(mFileChooser.getSelectedFile());
			}
		}
	};

	ActionListener mControllerActionListener = new ActionListener() {

		boolean isPlayed = false;

		@Override
		public void actionPerformed(ActionEvent e) {
			mStepsCount.setText(getStepsCountText());
			if (e.getSource().equals(mPlayBtn)) {
				if (isPlayed) {
					mPlayBtn.setIcon(mIconPlay);
					mListener.onPause();
					for (Timeline animation : animations) {
						animation.pause();
					}
				} else {
					mPlayBtn.setIcon(mIconPause);
					mListener.onPlay();
					for (Timeline animation : animations) {
						animation.play();
					}
				}
				isPlayed = !isPlayed;
			} else if (e.getSource().equals(mPrevBtn)) {
				mListener.onPrevClicked();
			} else if (e.getSource().equals(mNextBtn)) {
				mListener.onNextClicked();
			}
		}
	};

	public BaseFrame() {
		super("Life in Cube");
		mParamsDialog = new SettingsDialog(BaseFrame.this);
		mParamsDialog.setModal(true);
		initFileChooser();
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setSize(1000, 1000);
		initMenu();
		final Box box = Box.createVerticalBox();
		box.add(createController());
		panel = new JFXPanel();
		box.add(panel);
		Platform.runLater(new Runnable() {
			@Override
			public void run() {
				panel.setScene(initFX());
			}
		});

//		box.add(panel);
		setContentPane(box);

		final double half = (double) mSize / 2;

		//it may be taken as a parameter
		controller = new Controller(mParamsDialog.getXValue(),
									mParamsDialog.getYValue(),
									mParamsDialog.getZValue(),
									mParamsDialog.getToLiveValue(),
									mParamsDialog.getToDieValue(),
									mParamsDialog.getToBornValue());
		controller.addedCells().addListener(new ListChangeListener<Cell>() {
			@Override
			public void onChanged(Change<? extends Cell> change) {
				while (change.next()) {
					mStepsCount.setText(getStepsCountText());
					List<? extends Cell> addedSubList = change.getAddedSubList();
					System.out.println("Added : " + addedSubList.size());
					if (change.wasAdded()) {
						for (Cell c : addedSubList) {
							if (c != null) {
								final int i = c.getX();
								final int j = c.getY();
								final int k = c.getZ();

								final double r = (double) i / mSize;
								final double g = (double) j / mSize;
								final double b = (double) k / mSize;
								double a = 1.0 - length((i - half) / mSize, (j - half) / mSize, (k - half) / mSize);

								if (a > 1.0) {
									a = 1.0;
								}
								if (a < 0.0) {
									a = 0.2;
								}

								final double alpha = 1.0;
								System.out.println(i + " " +j +" " + k);

								Cube cube = field[i][j][k];
								cube.setColor(new Color(r, g, b, 1.0));
							}
						}
					}
				}
			}
		}

		);

		controller.removedCells().addListener(new ListChangeListener<Cell>() {
			@Override
			public void onChanged(Change<? extends Cell> change) {
				while (change.next()) {
					mStepsCount.setText(getStepsCountText());
					System.out.println("Removed : " + change.getAddedSize());
					if (change.wasAdded()) {
						for (Cell c : change.getAddedSubList()) {
							if (c != null) {
								int i = c.getX();
								int j = c.getY();
								int k = c.getZ();

								field[i][j][k].setColor(new Color(0.0, 0.0, 0.0, 0.0));
							}
						}
					}
				}
			}
		}

		);

		mListener = controller;
	}

	private void initMenu() {
		JMenu menu = new JMenu("Menu");
		JMenuBar menuBar = new JMenuBar();
		menuBar.add(menu);
		JMenuItem showParamsItem = new JMenuItem("Params");
		showParamsItem.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				mParamsDialog.setVisible(true);
			}
		});
		menu.add(showParamsItem);
		setJMenuBar(menuBar);
	}

	private Container createController() {

		JPanel pane = new JPanel();

		Box controller = Box.createHorizontalBox();
		mPrevBtn = new JButton(mIconPrev);
		mPrevBtn.addActionListener(mControllerActionListener);
		mNextBtn = new JButton(mIconNext);
		mNextBtn.addActionListener(mControllerActionListener);
		mPlayBtn = new JButton(mIconPlay);
		mPlayBtn.addActionListener(mControllerActionListener);


		controller.add(mPrevBtn);
		controller.add(mPlayBtn);
		controller.add(mNextBtn);

		pane.add(controller, BorderLayout.CENTER);

		JPanel leftPanel = new JPanel();
		leftPanel.add(mStepsCount);
		leftPanel.setAlignmentX(JPanel.RIGHT_ALIGNMENT);
		pane.add(leftPanel, BorderLayout.EAST);

		return pane;
	}

	private void initFileChooser() {
		mFileChooser = new JFileChooser(".");

		FileFilter filter = new FileFilter() {

			@Override
			public String getDescription() {
				return FILE_SUFFIX_CONFIG;
			}

			@Override
			public boolean accept(File f) {
				return (f.isDirectory()) || f.getName().endsWith(FILE_SUFFIX_CONFIG);
			}
		};
		mFileChooser.setFileFilter(filter);
	}

	private Scene initFX() {
		field = new Cube[mParamsDialog.getXValue()][mParamsDialog.getYValue()][mParamsDialog.getZValue()];
		root = new Group();
		Scene scene = new Scene(root, getWidth(), getHeight(), true);
		scene.setFill(new Color(1.0, 1.0, 1.0, 1.0));

		
		arcBall = new ArcBall(getWidth(), getHeight());

		final PerspectiveCamera cam = new PerspectiveCamera();
		scene.setCamera(cam);

		root.getTransforms().addAll(
				new Translate(getWidth() / 2, getHeight() / 2)
		);

		sceneContent = new Group();
		content = create3dContent(mParamsDialog.getXValue(), mParamsDialog.getYValue(), mParamsDialog.getZValue());

		scene.setOnMouseDragged(new EventHandler<MouseEvent>() {
			@Override
			public void handle(MouseEvent mouseEvent) {
				arcBall.drag(mouseEvent.getSceneX(), mouseEvent.getSceneY());
				Quaternion q = arcBall.getQuatNow();
				Matrix4x4 m = new Matrix4x4(q);
				Rotate rotate = setRotate(m);
				sceneContent.setRotationAxis(rotate.getAxis());
				sceneContent.setRotate(rotate.getAngle());
			}
		});

		scene.setOnMousePressed(new EventHandler<MouseEvent>() {
			@Override
			public void handle(MouseEvent mouseEvent) {
				arcBall.begin(mouseEvent.getSceneX(), mouseEvent.getSceneY());
			}
		});

		scene.setOnMouseReleased(new EventHandler<MouseEvent>() {
			@Override
			public void handle(MouseEvent mouseEvent) {
				arcBall.end();
				Quaternion q = arcBall.getQuatNow();
				Matrix4x4 m = new Matrix4x4(q);
				rotationMatrix.mult(m);

				Rotate rotate = setRotate(rotationMatrix);
				content.setRotationAxis(rotate.getAxis());
				content.setRotate(rotate.getAngle());

				sceneContent.setRotationAxis(new Point3D(1, 0, 0));
				sceneContent.setRotate(0);
			}
		});

		scene.setOnScroll(new EventHandler<ScrollEvent>() {
			@Override
			public void handle(ScrollEvent scrollEvent) {
				if ((scrollEvent.getDeltaY() < 0 && sceneScale > 0.02) || scrollEvent.getDeltaY() >= 0) {
					sceneScale = sceneScale + 0.001 * scrollEvent.getDeltaY();
					content.setScaleX(sceneScale);
					content.setScaleY(sceneScale);
					content.setScaleZ(sceneScale);
				}
			}
		});
		sceneContent.getChildren().addAll(content);
		root.getChildren().add(sceneContent);
		return scene;
	}

	double length(double x, double y, double z) {
		return Math.sqrt(Math.abs(x) * Math.abs(x) + Math.abs(y) * Math.abs(y) + Math.abs(z) * Math.abs(z));
	}

	public Group create3dContent(int sizeX, int sizeY, int sizeZ) {
		ArrayList<Cube> cubes = new ArrayList<>();
		Color clr = new Color(1.0, 1.0, 1.0, 0.0);
		int dx = 60;
		for (int i = 0; i < sizeX; i++) {
			for (int j = 0; j < sizeY; j++) {
				for (int k = 0; k < sizeZ; k++) {
					double translateX = (i - (sizeX / 2)) * dx;
					double translateY = (j - (sizeY / 2)) * dx;
					double translateZ = (k - (sizeZ / 2)) * dx;
					if (allCube) {
						clr = new Color((double) i / sizeX, (double) j / sizeY, (double) k / sizeZ, 1.0);
					}
					Cube cube = create3dCube(translateX, translateY, translateZ, clr);
					field[i][j][k] = cube;
					cubes.add(cube);
					if (needAnimation) {
						animations.add(lifeAnimation.getAnimation(cube, new Point3D(translateX, translateY, translateZ),
								new Point3D((i - (sizeX / 2)),
						(j - (sizeY / 2)),
						(k - (sizeZ / 2)))
								));
					}

				}
			}
		}

		for (Timeline timeline : animations) {
			timeline.play();
		}

		return new Group(cubes.toArray(new Node[cubes.size()]));
	}

	public Cube create3dCube(double px, double py, double pz, Color clr) {

		Cube c = new Cube(50, clr, 1);
		c.setTranslateX(px);
		c.setTranslateY(py);
		c.setTranslateZ(pz);

		return c;
	}

	private Rotate setRotate(Matrix4x4 rotMatrix) {
		Matrix3x3 rot = new Matrix3x3(rotMatrix);
		double trace = rot.trace();
		double angle = Math.acos(0.5 * (trace - 1));
		double degreeAngle = Math.toDegrees(angle);

		Rotate rotate = new Rotate();

		if (angle < 1e-5) {
			rotate.setAxis(new Point3D(1, 0, 0));

		}
		else {
			Point3D axis = getAxis(rot, angle);
			rotate.setAxis(axis);
		}

		rotate.setAngle(degreeAngle);
		return rotate;
	}

	private Point3D getAxis(Matrix3x3 rot, double angle) {
		Matrix3x3 transposed = rot.getTransposed();
		rot.substract(transposed);
		rot.multScalar(0.5 / Math.sin(angle));
		return new Point3D(rot.get(1, 2), -rot.get(2, 0), rot.get(0, 1));
	}

	@Override
	public void repaint(int x, int y, int width, int height) {
		if (root != null) {
			root.getTransforms().clear();
			root.getTransforms().addAll(
					new Translate(getWidth() / 2, getHeight() / 2)
			);
			arcBall.setWindowSize(getWidth(), getHeight());
		}
		super.repaint(x, y, width, height);
	}

	public void refresh() {
		field = new Cube[mParamsDialog.getXValue()][mParamsDialog.getYValue()][mParamsDialog.getZValue()];
//		root.getChildrenUnmodifiable().clear();
		panel.removeAll();
		panel.revalidate();
		controller.refresh(mParamsDialog.getXValue(),
				mParamsDialog.getYValue(),
				mParamsDialog.getZValue(),
				mParamsDialog.getToLiveValue(),
				mParamsDialog.getToDieValue(),
				mParamsDialog.getToBornValue());
		Platform.runLater(new Runnable() {
			@Override
			public void run() {
				panel.setScene(initFX());
			}
		});
	}


	public String getStepsCountText() {
		return "Steps count : " + controller.getStep();
	}
}